package com.kakarote.oa.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.util.TypeUtils;
import com.kakarote.core.common.enums.SystemCodeEnum;
import com.kakarote.core.exception.CrmException;
import com.kakarote.core.redis.Redis;
import com.kakarote.core.servlet.BaseServiceImpl;
import com.kakarote.core.utils.BaseUtil;
import com.kakarote.core.utils.UserUtil;
import com.kakarote.oa.common.OaCodeEnum;
import com.kakarote.oa.entity.PO.OaLogFieldNumberData;
import com.kakarote.oa.entity.VO.OaLogTemplateFieldVO;
import com.kakarote.oa.mapper.OaLogFieldNumberDataMapper;
import com.kakarote.oa.service.IOaLogFieldNumberDataService;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;

/**
 * @author wanli
 * @description 针对表【wk_oa_log_field_number_data(自定义编号字段存值表)】的数据库操作Service实现
 * @createDate 2022-07-05 18:08:18
 */
@Service
public class OaLogFieldNumberDataServiceImpl extends BaseServiceImpl<OaLogFieldNumberDataMapper, OaLogFieldNumberData> implements IOaLogFieldNumberDataService {

    @Autowired
    private Redis redis;

    /**
     * 锁定redis时间
     */
    private static final Long LOCK_TIME = 5L;

    /**
     * redis锁定的key
     */
    private static final String SERIAL_NUMBER = "SERIAL_NUMBER:";

    /**
     * 生成唯一编号字段
     *
     * @param dataMap 字段值列表
     * @param filedVO 字段信息
     * @return number
     */
    @Override
    public String generateNumber(OaLogTemplateFieldVO filedVO, Map<String, Object> dataMap) {
        List<FieldNumberSetting> fieldNumberSettingList = JSON.parseArray(filedVO.getOptions(), FieldNumberSetting.class);
        Object batchId = dataMap.get("batchId");
        Object dataId = dataMap.get("dataId");
        if (batchId == null) {
            return null;
        }
        Optional<OaLogFieldNumberData> fieldNumberDataOptional = lambdaQuery()
                .eq(OaLogFieldNumberData::getFieldId, filedVO.getFieldId())
                .eq(OaLogFieldNumberData::getBatchId, batchId)
                .oneOpt();
        if (fieldNumberDataOptional.isPresent()) {
            return fieldNumberDataOptional.get().getFieldValue();
        }
        //自动计数类型字段必须存在且只能存在一个
        long count = fieldNumberSettingList.stream().filter(data -> Objects.equals(1, data.getType())).count();
        if (count != 1) {
            return null;
        }
        Integer fieldNumber = null;
        StringBuilder builder = new StringBuilder();
        for (FieldNumberSetting numberSetting : fieldNumberSettingList) {
            Integer type = numberSetting.getType();
            switch (type) {
                case 1:
                    if (Objects.equals(1, numberSetting.getAllowedInput())) {
                        String data = TypeUtils.castToString(dataMap.get(filedVO.getFieldName()));
                        if (StrUtil.isNotEmpty(data)) {
                            if (redis.setNx(SERIAL_NUMBER + data + ":" + filedVO.getFieldId(), LOCK_TIME, 1)) {
                                boolean exists = lambdaQuery().eq(OaLogFieldNumberData::getFieldId, filedVO.getFieldId()).eq(OaLogFieldNumberData::getFieldValue, data).exists();
                                if (exists) {
                                    throw new CrmException(OaCodeEnum.CRM_SERIAL_NUMBER_DATA_EXIST_ERROR);
                                }
                            } else {
                                throw new CrmException(OaCodeEnum.CRM_SERIAL_NUMBER_DATA_EXIST_ERROR);
                            }
                            OaLogFieldNumberData customData = new OaLogFieldNumberData();
                            customData.setFieldNumber(0);
                            customData.setFieldId(filedVO.getFieldId());
                            customData.setFieldValue(data);
                            customData.setBatchId(batchId.toString());
                            customData.setCreateTime(LocalDateTimeUtil.now());
                            save(customData);
                            return data;
                        }
                    }
                    if (numberSetting.getResetType() == null) {
                        numberSetting.setResetType(4);
                    }
                    if (numberSetting.getStepNumber() == null) {
                        numberSetting.setStepNumber(1);
                    }
                    if (!NumberUtil.isNumber(numberSetting.getStartNumber())) {
                        numberSetting.setStartNumber("0001");
                    }
                    Date startDate, endDate;
                    Date date = new Date();
                    switch (numberSetting.getResetType()) {
                        case 1:
                            startDate = DateUtil.beginOfDay(date);
                            endDate = DateUtil.endOfDay(date);
                            break;
                        case 2:
                            startDate = DateUtil.beginOfMonth(date);
                            endDate = DateUtil.endOfMonth(date);
                            break;
                        case 3:
                            startDate = DateUtil.beginOfYear(date);
                            endDate = DateUtil.endOfYear(date);
                            break;
                        case 4:
                            startDate = null;
                            endDate = null;
                            break;
                        default:
                            throw new CrmException(SystemCodeEnum.SYSTEM_NO_VALID);
                    }
                    Integer queryMaxNumber = getBaseMapper().queryMaxNumber(filedVO.getFieldId(), startDate, endDate);
                    fieldNumber = getSerialNumber(100, filedVO.getFieldId(), queryMaxNumber, numberSetting);
                    String number = fieldNumber.toString();
                    if (number.length() < numberSetting.getStartNumber().length()) {
                        number = String.format("%0" + numberSetting.getStartNumber().length() + "d", fieldNumber);
                    }
                    builder.append(number);
                    break;
                case 2:
                    builder.append(numberSetting.getValue().replace("-", ""));
                    break;
                case 3:
                    Object object = dataMap.get(StrUtil.toCamelCase(numberSetting.getValue()));
                    if (object == null) {
                        break;
                    }
                    //时间类型字段
                    if (Arrays.asList("yyyyMMdd", "yyyyMM", "yyyy").contains(numberSetting.getTextFormat())) {
                        if (object instanceof Date) {
                            builder.append(DateUtil.format((Date) object, numberSetting.getTextFormat()));
                        } else if (object instanceof LocalDate) {
                            builder.append(LocalDateTimeUtil.format((LocalDate) object, numberSetting.getTextFormat()));
                        } else if (object instanceof LocalDateTime) {
                            builder.append(LocalDateTimeUtil.format((LocalDateTime) object, numberSetting.getTextFormat()));
                        } else {
                            builder.append(DateUtil.parseDate((String) object).toString(numberSetting.getTextFormat()));
                        }
                    } else {
                        builder.append(object.toString().replace("-", ""));
                    }
                    break;
                default:
                    break;
            }
            builder.append("-");
        }
        builder.deleteCharAt(builder.length() - 1);
        //自定义编号类型字段不能超过200字符
        int index = 200;
        if (builder.length() > index) {
            throw new CrmException(OaCodeEnum.CRM_SERIAL_NUMBER_FIELD_LENGTH_ERROR);
        }
        if (fieldNumber == null) {
            throw new CrmException(SystemCodeEnum.SYSTEM_ERROR);
        }
        OaLogFieldNumberData data = new OaLogFieldNumberData();
        data.setFieldNumber(fieldNumber);
        data.setFieldId(filedVO.getFieldId());
        data.setFieldValue(builder.toString());
        data.setBatchId(batchId.toString());
        data.setCreateTime(LocalDateTimeUtil.now());
        save(data);
        Dict dict = Dict.create()
                .set("fieldId", data.getFieldId())
                .set("dataId", dataId);
        dict.set("value", data.getFieldValue());
        if (filedVO.getFieldType() == 0) {
            dict.set("name", filedVO.getFieldName());
            dict.set("id", BaseUtil.getNextId());
            dict.set("createUserId", UserUtil.getUserId());
            Integer integer = getBaseMapper().selectBatchData(dict);
            if (ObjectUtil.equals(integer, 1)) {
                getBaseMapper().updateBatchData(dict);
            } else {
                getBaseMapper().saveBatchData(dict);
            }
        }
        return data.getFieldValue();
    }

    private Integer getSerialNumber(Integer depth, Long fieldId, Integer startNumber, FieldNumberSetting numberSetting) {
        //最多重试100次
        if (depth < 0) {
            throw new CrmException(SystemCodeEnum.SYSTEM_ERROR);
        }
        int initNumber = NumberUtil.parseInt(numberSetting.getStartNumber());
        if (startNumber == null || initNumber > startNumber) {
            startNumber = initNumber;
        } else {
            startNumber = startNumber + numberSetting.getStepNumber();
        }
        String str = ":";

        if (redis.setNx(SERIAL_NUMBER + fieldId + str + startNumber, LOCK_TIME, 1)) {
            return startNumber;
        } else {
            return getSerialNumber(--depth, fieldId, startNumber, numberSetting);
        }
    }

    @Data
    public static class FieldNumberSetting {
        //类型 1 自动计数 2 固定值 3 表单内字段
        private Integer type;

        //当type为2时，为固定的值，当type为3时，展示fieldName
        private String value;

        //type为3，并且为日期和日期时间类型时需要，为需要格式化的日期格式
        private String textFormat;

        //当type为1时需要，起始编号
        private String startNumber;

        //stepNumber  当type为1时需要，递增数;
        private Integer stepNumber;

        //当type为1时需要，1 每天 2 每月 3 每年 4 从不;
        private Integer resetType;

        //是否允许手输，1代表允许，默认为0
        private Integer allowedInput = 0;
    }
}




